package com.gitee.wsl.android.ext.device

//package com.dylanc.longan

import android.Manifest.permission.*
import android.annotation.SuppressLint
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.content.IntentFilter
import android.net.ConnectivityManager
import android.net.Network
import android.net.NetworkCapabilities
import android.net.NetworkCapabilities.*
import android.net.NetworkRequest
import android.net.wifi.ScanResult
import android.net.wifi.WifiManager
import android.os.Build
import android.text.format.Formatter.formatIpAddress
import androidx.annotation.RequiresPermission
import androidx.core.content.getSystemService
import androidx.lifecycle.LiveData
import com.gitee.wsl.android.ext.ApplicationInit.application
import com.gitee.wsl.android.ext.device.NetworkExt.IPV6_HEX_COMPRESSED_PATTERN
import com.gitee.wsl.android.ext.device.NetworkExt.IPV6_STD_PATTERN
import com.gitee.wsl.android.ext.device.NetworkExt.getMacBytes
import com.gitee.wsl.android.ext.device.NetworkExt.isIpAddressLocalNetwork
import com.gitee.wsl.android.ext.device.NetworkExt.isIpAddressLocalhost
import com.gitee.wsl.android.ext.device.NetworkExt.scanTcpAddress
import com.gitee.wsl.android.ext.device.NetworkExt.scanUpdAddress
import com.gitee.wsl.transform.validator.string.StringValidator
import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.callbackFlow
import timber.log.Timber
import java.io.IOException
import java.net.*
import java.util.*
import java.util.concurrent.Executors
import java.util.concurrent.TimeUnit
import java.util.regex.Pattern
import java.util.stream.Collectors
import kotlin.experimental.and
import kotlin.random.Random
import java.lang.StringBuilder

object NetworkExt{
    @get:RequiresPermission(ACCESS_NETWORK_STATE)
    val isNetworkAvailable: Boolean
        get() = application.getSystemService<ConnectivityManager>()?.run {
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
                getNetworkCapabilities(activeNetwork)?.run {
                    hasCapability(NET_CAPABILITY_INTERNET) && hasCapability(NET_CAPABILITY_VALIDATED)
                }
            } else {
                @Suppress("DEPRECATION")
                activeNetworkInfo?.isConnectedOrConnecting
            }
        } ?: false

    @get:RequiresPermission(ACCESS_NETWORK_STATE)
    val isWifiConnected: Boolean
        get() = application.getSystemService<ConnectivityManager>()?.run {
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
                getNetworkCapabilities(activeNetwork)?.hasTransport(TRANSPORT_WIFI)
            } else {
                @Suppress("DEPRECATION")
                activeNetworkInfo?.run { isConnected && type == ConnectivityManager.TYPE_WIFI }
            }
        } ?: false

    @get:RequiresPermission(ACCESS_NETWORK_STATE)
    val isMobileData: Boolean
        get() = application.getSystemService<ConnectivityManager>()?.run {
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
                getNetworkCapabilities(activeNetwork)?.hasTransport(TRANSPORT_CELLULAR)
            } else {
                @Suppress("DEPRECATION")
                activeNetworkInfo?.run { isAvailable && type == ConnectivityManager.TYPE_MOBILE }
            }
        } ?: false

    @get:RequiresPermission(ACCESS_WIFI_STATE)
    inline val isWifiEnabled: Boolean
        get() = application.getSystemService<WifiManager>()?.isWifiEnabled == true


    private const val IP4_PATTERN = "^([01]?\\d\\d?|2[0-4]\\d|25[0-5])\\." +
            "([01]?\\d\\d?|2[0-4]\\d|25[0-5])\\." +
            "([01]?\\d\\d?|2[0-4]\\d|25[0-5])\\." +
            "([01]?\\d\\d?|2[0-4]\\d|25[0-5])$"

    private const val IP6_PATTERN =
        "^s*((([0-9A-Fa-f]{1,4}:){7}([0-9A-Fa-f]{1,4}|:))|(([0-9A-Fa-f]{1,4}:){6}(:[0-9A-Fa-f]{1,4}|((25[0-5]|2[0-4]d|1dd|[1-9]?d)(.(25[0-5]|2[0-4]d|1dd|[1-9]?d)){3})|:))|(([0-9A-Fa-f]{1,4}:){5}(((:[0-9A-Fa-f]{1,4}){1,2})|:((25[0-5]|2[0-4]d|1dd|[1-9]?d)(.(25[0-5]|2[0-4]d|1dd|[1-9]?d)){3})|:))|(([0-9A-Fa-f]{1,4}:){4}(((:[0-9A-Fa-f]{1,4}){1,3})|((:[0-9A-Fa-f]{1,4})?:((25[0-5]|2[0-4]d|1dd|[1-9]?d)(.(25[0-5]|2[0-4]d|1dd|[1-9]?d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){3}(((:[0-9A-Fa-f]{1,4}){1,4})|((:[0-9A-Fa-f]{1,4}){0,2}:((25[0-5]|2[0-4]d|1dd|[1-9]?d)(.(25[0-5]|2[0-4]d|1dd|[1-9]?d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){2}(((:[0-9A-Fa-f]{1,4}){1,5})|((:[0-9A-Fa-f]{1,4}){0,3}:((25[0-5]|2[0-4]d|1dd|[1-9]?d)(.(25[0-5]|2[0-4]d|1dd|[1-9]?d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){1}(((:[0-9A-Fa-f]{1,4}){1,6})|((:[0-9A-Fa-f]{1,4}){0,4}:((25[0-5]|2[0-4]d|1dd|[1-9]?d)(.(25[0-5]|2[0-4]d|1dd|[1-9]?d)){3}))|:))|(:(((:[0-9A-Fa-f]{1,4}){1,7})|((:[0-9A-Fa-f]{1,4}){0,5}:((25[0-5]|2[0-4]d|1dd|[1-9]?d)(.(25[0-5]|2[0-4]d|1dd|[1-9]?d)){3}))|:)))(%.+)?s*$"

    private const val PORT_OR_RANGE_PATTERN = "[0-9]{1,5}|([0-9]{1,5}\\-[0-9]{1,5})"

    internal const val IPV6_STD_PATTERN ="^(?:[0-9a-fA-F]{1,4}:){7}[0-9a-fA-F]{1,4}$"


    internal const val IPV6_HEX_COMPRESSED_PATTERN ="^((?:[0-9A-Fa-f]{1,4}(?::[0-9A-Fa-f]{1,4})*)?)::((?:[0-9A-Fa-f]{1,4}(?::[0-9A-Fa-f]{1,4})*)?)$"


    private val ip4Pattern = Pattern.compile(IP4_PATTERN)
    private val ip6Pattern = Pattern.compile(IP6_PATTERN)
    private val portOrRangePattern = Pattern.compile(PORT_OR_RANGE_PATTERN)

    fun getNetworkInterfaces(): List<NetworkInterface> {
        return try {
            Collections.list(NetworkInterface.getNetworkInterfaces())
        } catch (ex: java.lang.Exception) {
            arrayListOf()
        }
    }

    fun getDeviceIP4(): String {
        try {
            val en = NetworkInterface.getNetworkInterfaces()
            while (en.hasMoreElements()) {
                val intf = en.nextElement()
                val enumIpAddr = intf.inetAddresses
                while (enumIpAddr.hasMoreElements()) {
                    val inetAddress = enumIpAddr.nextElement()
                    if (!inetAddress.isLoopbackAddress && inetAddress is Inet4Address) {
                        return inetAddress.getHostAddress() ?: ""
                    }
                }
            }
        } catch (ex: SocketException) {
            ex.printStackTrace()
        }
        return ""
    }

    fun isVPNConnected(context: Context): Boolean {
        val cm = context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
        val networks = cm.allNetworks
        for (i in networks.indices) {
            val caps = cm.getNetworkCapabilities(networks[i])
            if (caps?.hasTransport(NetworkCapabilities.TRANSPORT_VPN) == true) {
                return true
            }
        }
        return false
    }

    // https://stackoverflow.com/questions/12401108/how-to-check-programmatically-if-hotspot-is-enabled-or-disabled
    /*fun isHotspotEnabled(context: Context): Boolean {
        val wifiManager = context.getSystemService(Context.WIFI_SERVICE) as WifiManager
        val AP_STATE_ENABLING = 12
        val AP_STATE_ENABLED = 13
        return try {
            val method = wifiManager.javaClass.getDeclaredMethod("getWifiApState")
            method.isAccessible = true
            val state = method.invoke(wifiManager, null as Array<Any?>?) as Int
            state == AP_STATE_ENABLING || state == AP_STATE_ENABLED
        } catch (e: ReflectiveOperationException) {
            // if there is some reflection issue, let's just assume it's enabled
            // let's not take the risk to tell the user it's not enabled when it could be
            true
        }
    }*/


    fun subnetContains(subnet: String, ip: String): Boolean {
        val parts1 = subnet.split("/")
        val mask = maskLengthToIP4(parts1[1].toInt())
        return try {
            isSubnetOverlapped(mask, parts1[0], mask, ip)
        } catch (e: Exception) {
            false
        }
    }

    fun randomFirstIP(type: String = "A"): String {
        if (type == "A") {
            return "10.${Random.nextInt(0, 255)}.${Random.nextInt(0, 255)}.1"
        } else if (type == "B") {
            return "172.${Random.nextInt(16, 31)}.${Random.nextInt(0, 255)}.1"
        }

        return "192.168.${Random.nextInt(0, 255)}.1"
    }

    fun isSubnetOverlapped(mask1: String, ip1: String, mask2: String, ip2: String): Boolean {
        return try {
            val maskInt1 = ip4ToLong(mask1)
            val maskInt2 = ip4ToLong(mask2)

            val maskToUse = if (maskInt1 < maskInt2) maskInt1 else maskInt2
            val addr1 = ip4ToLong(ip1) and maskToUse
            val addr2 = ip4ToLong(ip2) and maskToUse

            return addr1 == addr2
        } catch (e: Exception) {
            false
        }
    }

    fun maskLengthToIP4(maskLength: Int): String {
        return longToIP4(maskLengthToLong(maskLength))
    }

    fun maskLengthToLong(maskLength: Int): Long {
        val full: Long = 0xFFFFFFFF
        val subnet: Long = (0x01.toLong() shl (32 - maskLength)) - 1
        return full xor subnet
    }

    fun isIP4(input: String): Boolean {
        return checkPattern(input, ip4Pattern)
    }

    fun isIP6(input: String): Boolean {
        return checkPattern(input, ip6Pattern)
    }

    fun isIP4Net(input: String): Boolean {
        val split = input.split('/')
        if (split.size != 2) {
            return false
        }

        val len = split[1].toIntOrNull()
        return isIP4(split[0]) && len != null && len >= 0 && len <= 32
    }

    fun isIP6Net(input: String): Boolean {
        val split = input.split('/')
        if (split.size != 2) {
            return false
        }

        val len = split[1].toIntOrNull()
        return isIP6(split[0]) && len != null && len > 32 && len <= 128
    }

    fun isIP(input: String): Boolean {
        if (!isIP4(input)) {
            return isIP6(input)
        }

        return true
    }

    fun isIPNet(input: String): Boolean {
        if (!isIP4Net(input)) {
            return isIP6Net(input)
        }

        return true
    }

    fun isPrivateIP6(ip: String): Boolean {
        try {
            val a = Inet6Address.getByName(ip)
            val isUniqueLocal = a.address[0].and(1.toByte()) == 1.toByte() // https://tools.ietf.org/html/rfc4193
            return a.isLinkLocalAddress || isUniqueLocal || a.isSiteLocalAddress
        } catch (e: Exception) {
        }
        return false
    }

    fun isSiteLocalIP4(ip: String): Boolean {
        try {
            val a = Inet4Address.getByName(ip)
            return a.isSiteLocalAddress
        } catch (e: Exception) {
        }
        return false
    }

    fun isPrivateIP4(ip: String): Boolean {
        try {
            val a = Inet4Address.getByName(ip)
            return a.isLoopbackAddress || a.isSiteLocalAddress
        } catch (e: Exception) {
        }
        return false
    }

    fun isIPWithOptionalPort(input: String): Boolean {
        if (input.contains(":")) {
            if (isIP6(input)) {
                return true
            }

            val ip: String
            val port: String
            if (input.contains("]:") && input.startsWith("[")) {
                val split = input.split("]:")
                ip = split[0].trimStart('[')
                port = split[1]
            } else {
                val split = input.split(":")
                ip = split[0]
                port = split[1]
            }

            return isIP(ip) && isPortOrPortRangeMultiple(port)
        }

        return isIP(input)
    }

    fun isPortOrPortRange(input: String): Boolean {
        if (checkPattern(input, portOrRangePattern)) {
            return try {
                if (input.contains("-")) {
                    val parts = input.split("-")
                    val left = parts[0].toInt()
                    val right = parts[1].toInt()
                    left in 1 until right && left < 65536 && right > 0 && right < 65536
                } else {
                    input.toInt() in 1..65535
                }
            } catch (e: Exception) {
                false
            }
        }
        return false
    }

    fun isPort(input: String): Boolean {
        return try {
            input.toInt() in 1..65535
        } catch (e: Exception) {
            false
        }
    }

    /**
     * Get a random socket port and close it
     */
    fun getRandomPort(): Int {
        val serverSocket = java.net.ServerSocket(0)
        val port = serverSocket.localPort
        serverSocket.close()
        return port
    }

    fun isNetWithOptionalPort(input: String): Boolean {
        if (input.contains(":")) {
            if (isIP6Net(input)) {
                return true
            }

            val ip: String
            val port: String
            if (input.contains("]:") && input.startsWith("[")) {
                val split = input.split("]:")
                ip = split[0].trimStart('[')
                port = split[1]
            } else {
                val split = input.split(":")
                ip = split[0]
                port = split[1]
            }

            return isIPNet(ip) && isPortOrPortRangeMultiple(port)
        }

        return isIPNet(input)
    }

    fun isDomainWithOptionalPort(input: String): Boolean {
        if (input.contains(":")) {
            val split = input.split(":")
            val domain = split[0]
            val port = split[1]

            return isDomainName(domain) && isPortOrPortRangeMultiple(port)
        }

        return isDomainName(input)
    }

    fun isDomainName(input: String): Boolean {
       /* return try {
            InternetDomainName.from(input.trimStart('*', '.'))
            true
        } catch (e: Exception) {
            false
        }*/
        return false
    }


    fun isPortOrPortRangeMultiple(input: String): Boolean {
        if (input.contains(",")) {
            val split = input.split(",")
            return split.all { isPortOrPortRange(it) }
        }

        return isPortOrPortRange(input)
    }


    @get:Throws(IOException::class)
    val privateNetworkIpAddress: InetAddress
        /**
         *
         * @return return the ip address of the device
         * @throws IOException in case of I/O errors
         */
        get() {
            val ipAddresses = ipAddresses
            if (ipAddresses.isEmpty()) {
                throw IOException("No IP address were found")
            }
            if (ipAddresses.size == 1) {
                return ipAddresses[0]
            }
            ipAddresses.sortedWith(NetworkExt::compare)
            return ipAddresses[0]
        }

    private fun compare(address1: InetAddress, address2: InetAddress): Int {
        if (is16BlockPrivateAddress(address1)) {
            return -1
        } else if (is16BlockPrivateAddress(address2)) {
            return 1
        }
        if (is24BlockPrivateAddress(address1)) {
            return -1
        } else if (is24BlockPrivateAddress(address2)) {
            return 1
        }
        if (is20BlockPrivateAddress(address1)) {
            return -1
        } else if (is20BlockPrivateAddress(address2)) {
            return 1
        }
        return address1.hostName.compareTo(address1.hostName)
    }

    private fun is16BlockPrivateAddress(address: InetAddress): Boolean {
        return isInRange(address, "192.168.0.0", "192.168.255.255")
    }

    private fun is24BlockPrivateAddress(address: InetAddress): Boolean {
        return isInRange(address, "10.0.0.0", "10.255.255.255")
    }

    private fun is20BlockPrivateAddress(address: InetAddress): Boolean {
        return isInRange(address, "172.16.0.0", "172.31.255.255")
    }

    private fun isInRange(
        address: InetAddress, lowestAddress: String,
        highestAddress: String
    ): Boolean {
        return isInRange(address, getAddress(lowestAddress), getAddress(highestAddress))
    }

    private fun isInRange(
        address: InetAddress, lowestAddress: InetAddress,
        highestAddress: InetAddress
    ): Boolean {
        val ipLo = ipToLong(lowestAddress)
        val ipHi = ipToLong(highestAddress)
        val ipToTest = ipToLong(address)
        return ipToTest in ipLo..ipHi
    }

    fun ipToLong(ip: InetAddress): Long {
        val octets = ip.address
        var result: Long = 0
        for (octet in octets) {
            result = result shl 8
            result = result or (octet.toInt() and 0xff).toLong()
        }
        return result
    }

    /**
     * from https://stackoverflow.com/questions/6064510/how-to-get-ip-address-of-the-device-from-code
     *
     * @return return the ip address of the device
     * @throws IOException in case of I/O errors
     */
    @get:Throws(IOException::class)
    val ipAddresses: List<InetAddress>
        get() {
            val interfaces = Collections.list(NetworkInterface.getNetworkInterfaces())
            return interfaces.stream()
                .flatMap { i: NetworkInterface ->
                    Collections.list(
                        i.inetAddresses
                    ).stream()
                }
                .filter { addr: InetAddress ->
                    !addr.isLoopbackAddress &&  // ipV4
                            !addr.hostAddress?.contains(":")!!
                }
                .collect(Collectors.toList())
        }
    val privateNetworkIpAddressOrNull: InetAddress?
        get() = try {
            privateNetworkIpAddress
        } catch (e: IOException) {
            null
        }

    /**
     * Get an available port
     *
     * @param inetAddress the address of the host
     * @param startingPort the port from which to start looking for
     * @return an available port
     */
    fun getAvailablePort(inetAddress: InetAddress, startingPort: Int=1): Int {
        var port = startingPort
        while (port < startingPort + 16 * 16) {
            try {
                ServerSocket(port, 0, inetAddress).use { serverSocket -> }
            } catch (e: IOException) {
                port++
                continue
            }
            return port
        }
        throw IllegalStateException("No available port was found")
    }

    /**
     * Returns a well formatted string of the given ip
     *
     * @param address the address
     * @return a well formatted string of the given ip
     */
    fun toString(address: InetAddress): String {
        return address.hostAddress?.replace("/", "") ?:""
    }

    fun getAddress(address: String): InetAddress {
        return try {
            InetAddress.getByName(address)
        } catch (e: UnknownHostException) {
            throw java.lang.IllegalArgumentException("Unknown host", e)
        }
    }

    @Throws(SocketException::class)
    fun listAllBroadcastAddresses(): List<InetAddress> {
        val broadcastList: MutableList<InetAddress> = ArrayList()
        val interfaces = NetworkInterface.getNetworkInterfaces()
        while (interfaces.hasMoreElements()) {
            val networkInterface = interfaces.nextElement()
            if (networkInterface.isLoopback || !networkInterface.isUp) {
                continue
            }
            networkInterface.interfaceAddresses.stream()
                .map { obj: InterfaceAddress -> obj.broadcast }
                .filter { obj: InetAddress? ->
                    Objects.nonNull(
                        obj
                    )
                }
                .forEach { e: InetAddress ->
                    broadcastList.add(
                        e
                    )
                }
        }
        return broadcastList
    }



    private fun longToIP4(ip: Long): String {
        return "${((ip shr 24) % 256)}.${((ip shr 16) % 256)}.${((ip shr 8) % 256)}.${(ip % 256)}"
    }

    private fun ip4ToLong(ip: String): Long {
        val octets = ip.split(".")
        return ((octets[0].toLong() shl 24) + (octets[1].toInt() shl 16)
                + (octets[2].toInt() shl 8) + octets[3].toInt())
    }

    private fun checkPattern(input: String, pattern: Pattern): Boolean {
        return if (input.isEmpty()) false else try {
            pattern.matcher(input).matches()
        } catch (e: Exception) {
            false
        }
    }

    @Throws(SocketException::class)
    fun getMacAddress(interfaceName:String="wlan0"): String? {
        var address: String? = null
        // 把当前机器上的访问网络接口的存入 Enumeration集合中
        val interfaces = NetworkInterface.getNetworkInterfaces()
        while (interfaces.hasMoreElements()) {
            val netWork = interfaces.nextElement()
            // 如果存在硬件地址并可以使用给定的当前权限访问，则返回该硬件地址（通常是 MAC）。
            val by = netWork.hardwareAddress
            if (by == null || by.isEmpty()) {
                continue
            }
            val builder = StringBuilder()
            for (b in by) {
                builder.append(String.format("%02X:", b))
            }
            if (builder.isNotEmpty()) {
                builder.deleteCharAt(builder.length - 1)
            }
            val mac = builder.toString()
            Timber.d( "interfaceName=" + netWork.name.toString() + ", mac=" + mac)
            // 从路由器上在线设备的MAC地址列表，可以印证设备Wifi的 name 是 wlan0
            if (netWork.name.equals(interfaceName)) {
                Timber.d( " interfaceName =" + netWork.name.toString() + ", mac=" + mac)
                address = mac
            }
        }
        return address
    }

    /**
     * Check if a port is open with UDP, note that this isn't reliable
     * as UDP will does not send ACKs
     *
     * @param ia            - address to scan
     * @param portNo        - port to scan
     * @param timeoutMillis - timeout
     * @return - true if port is open, false if not or unknown
     */
    fun scanUpdAddress(ia: InetAddress, portNo: Int, timeoutMillis: Int): Boolean {
        try {
            val bytes = ByteArray(128)
            val dp = DatagramPacket(bytes, bytes.size)
            val ds = DatagramSocket()
            ds.soTimeout = timeoutMillis
            ds.connect(ia, portNo)
            ds.send(dp)
            ds.isConnected
            ds.receive(dp)
            ds.close()
        } catch (e: SocketTimeoutException) {
            return true
        } catch (ignore: java.lang.Exception) {
        }
        return false
    }

    /**
     * Check if a port is open with TCP
     *
     * @param ia            - address to scan
     * @param portNo        - port to scan
     * @param timeoutMillis - timeout
     * @return - true if port is open, false if not or unknown
     */
    fun scanTcpAddress(ia: InetAddress, portNo: Int, timeoutMillis: Int): Boolean {
        var s: Socket? = null
        try {
            s = Socket()
            s.connect(InetSocketAddress(ia, portNo), timeoutMillis)
            return true
        } catch (e: IOException) {
            // Don't log anything as we are expecting a lot of these from closed ports.
        } finally {
            if (s != null) {
                try {
                    s.close()
                } catch (e: IOException) {
                    e.printStackTrace()
                }
            }
        }
        return false
    }

    /**
     * Check if the provided ip address refers to the localhost
     *
     * https://stackoverflow.com/a/2406819/315998
     *
     * @param addr - address to check
     * @return - true if ip address is self
     */
    fun isIpAddressLocalhost(addr: InetAddress): Boolean {

        // Check if the address is a valid special local or loop back
        return if (addr.isAnyLocalAddress || addr.isLoopbackAddress) true else try {
            NetworkInterface.getByInetAddress(addr) != null
        } catch (e: SocketException) {
            false
        }

        // Check if the address is defined on any interface
    }

    /**
     * Check if the provided ip address refers to the localhost
     *
     * https://stackoverflow.com/a/2406819/315998
     *
     * @param addr - address to check
     * @return - true if ip address is self
     */
    fun isIpAddressLocalNetwork(addr: InetAddress): Boolean {
        return addr.isSiteLocalAddress
    }

    /**
     * Convert a MAC string to bytes
     *
     * @param macStr - MAC string in IEEE802 format (either hyphen or colon separated)
     * eg: "01:23:45:67:89:AB" or "01-23-45-67-89-AB"
     * @return - MAC formatted in bytes
     * @throws IllegalArgumentException - if mac address is invalid
     */
    @Throws(java.lang.IllegalArgumentException::class)
    fun getMacBytes(macStr: String): ByteArray {
        val bytes = ByteArray(6)
        val hex = macStr.split("(\\:|\\-)").toTypedArray()
        require(hex.size == 6) { "Invalid MAC address." }
        try {
            for (i in 0..5) {
                bytes[i] = hex[i].toInt(16).toByte()
            }
        } catch (e: NumberFormatException) {
            throw java.lang.IllegalArgumentException("Invalid hex digit in MAC address.")
        }
        return bytes
    }

    /**
     * Returns true if device is connecting to the internet via a proxy, works for both Wi-Fi and Mobile Data.
     *
     * @return true if using proxy to connect to the internet.
     */
    fun isBehindProxy(): Boolean {
        return !(System.getProperty("http.proxyHost") == null || System.getProperty("http.proxyPort") == null)
    }

    /**
     * Return whether network is available using ping.
     *
     * Must hold `<uses-permission android:name="android.permission.INTERNET" />`
     *
     * @param ip The ip address.
     * @return `true`: yes<br></br>`false`: no
     */
    @RequiresPermission(INTERNET)
    fun isAvailableByPing(ip: String): Boolean {
        val realIp = ip.ifEmpty { "223.5.5.5" }
        val result: ShellExt.CommandResult = ShellExt.execRuntimeCommand(String.format("ping -c 1 %s", realIp))
        return result.resultCode == 0
    }

    /**
     * Return the ip address of broadcast.
     *
     * @return the ip address of broadcast
     */
    fun getBroadcastIpAddress(): String? {
        try {
            val nis = NetworkInterface.getNetworkInterfaces()
            //val adds: LinkedList<InetAddress> = LinkedList()
            while (nis.hasMoreElements()) {
                val ni = nis.nextElement()
                if (!ni.isUp || ni.isLoopback) continue
                val ias = ni.interfaceAddresses
                var i = 0
                val size = ias.size
                while (i < size) {
                    val ia = ias[i]
                    val broadcast = ia.broadcast
                    if (broadcast != null) {
                        return broadcast.hostAddress
                    }
                    i++
                }
            }
        } catch (e: SocketException) {
            e.printStackTrace()
        }
        return null
    }

    /**
     * Return the gate way by wifi.
     *
     * @return the gate way by wifi
     */
    @RequiresPermission(ACCESS_WIFI_STATE)
    fun getGatewayByWifi(): String? {
        return formatIpAddress(application.wifiManager.dhcpInfo.gateway)
    }

    /**
     * Return the net mask by wifi.
     *
     * @return the net mask by wifi
     */
    @RequiresPermission(ACCESS_WIFI_STATE)
    fun getNetMaskByWifi(): String {
        return formatIpAddress(application.wifiManager.dhcpInfo.netmask)
    }

    /**
     * Return the server address by wifi.
     *
     * @return the server address by wifi
     */
    @RequiresPermission(ACCESS_WIFI_STATE)
    fun getServerAddressByWifi(): String? {
        return formatIpAddress(application.wifiManager.dhcpInfo.serverAddress)
    }


    fun findWifiNetworkInterface(): NetworkInterface? {
        var enumeration: Enumeration<NetworkInterface?>? = null
        enumeration = try {
            NetworkInterface.getNetworkInterfaces()
        } catch (e: SocketException) {
            return null
        }
        var wlan0: NetworkInterface? = null
        while (enumeration!!.hasMoreElements()) {
            wlan0 = enumeration.nextElement()!!
            if (wlan0.name == "wlan0") {
                return wlan0
            }
        }
        return null
    }

    /**
     * parse InetAddress
     *
     * @param inetAddrBytes
     * @return
     */
    fun parseInetAddr(inetAddrBytes: ByteArray, offset: Int, count: Int): InetAddress? {
        var inetAddress: InetAddress? = null
        val sb = java.lang.StringBuilder()
        for (i in 0 until count) {
            sb.append((inetAddrBytes[offset + i].toInt() and 0xff).toString())
            if (i != count - 1) {
                sb.append('.')
            }
        }
        try {
            inetAddress = InetAddress.getByName(sb.toString())
        } catch (e: UnknownHostException) {
            e.printStackTrace()
        }
        return inetAddress
    }

    /**
     * parse bssid
     *
     * @param bssid the bssid like aa:bb:cc:dd:ee:ff
     * @return byte converted from bssid
     */
    fun parseBssid2bytes(bssid: String): ByteArray {
        val bssidSplits = bssid.split(":".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray()
        val result = ByteArray(bssidSplits.size)
        for (i in bssidSplits.indices) {
            result[i] = bssidSplits[i].toInt(16).toByte()
        }
        return result
    }

}

val globalWifiManager:WifiManager by lazy { application.wifiManager }

val globalConnectivityManager:ConnectivityManager by lazy { application.connectivityManager }

val Context.wifiManager:WifiManager
    get() = getSystemService(Context.WIFI_SERVICE) as WifiManager

val Context.connectivityManager:ConnectivityManager
    get() = getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager

fun String.isIPv4Address(): Boolean = NetworkExt.isIP4(this)

fun String.isIPv6StdAddress(): Boolean {
    return IPV6_STD_PATTERN.toRegex().matches(this)
}

fun String.isIPv6HexCompressedAddress(): Boolean {
    return  IPV6_HEX_COMPRESSED_PATTERN.toRegex().matches(this)
}

fun String.isIPv6Address(): Boolean {
    return  (isIPv6StdAddress() || isIPv6HexCompressedAddress())
}


inline val ScanResult.is24GHz: Boolean
    get() = frequency in 2400..2550

inline val ScanResult.is5GHz: Boolean
    get() = frequency in 5500..5800

class NetworkAvailableLiveData @RequiresPermission(ACCESS_NETWORK_STATE) constructor() : LiveData<Boolean>() {

    private val connectivityManager = application.connectivityManager

    @RequiresPermission(ACCESS_NETWORK_STATE)
    override fun onActive() {
        super.onActive()
        when {
            Build.VERSION.SDK_INT >= Build.VERSION_CODES.N ->
                connectivityManager.registerDefaultNetworkCallback(networkCallback)
            else ->
                connectivityManager.registerNetworkCallback(networkRequest, networkCallback)
        }
    }

    override fun onInactive() {
        super.onInactive()
        connectivityManager.unregisterNetworkCallback(networkCallback)
    }

    override fun setValue(value: Boolean) {
        if (this.value != value) {
            super.setValue(value)
        }
    }

    private val networkRequest by lazy {
        NetworkRequest.Builder()
            .addTransportType(TRANSPORT_CELLULAR)
            .addTransportType(TRANSPORT_ETHERNET)
            .addTransportType(TRANSPORT_WIFI)
            .build()
    }

    private val networkCallback = object : ConnectivityManager.NetworkCallback() {
        override fun onAvailable(network: Network) {
            if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
                postValue(true)
            }
        }

        override fun onCapabilitiesChanged(network: Network, networkCapabilities: NetworkCapabilities) {
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
                networkCapabilities.run {
                    postValue(hasCapability(NET_CAPABILITY_INTERNET) && hasCapability(NET_CAPABILITY_VALIDATED))
                }
            }
        }

        override fun onLost(network: Network) {
            postValue(false)
        }
    }
}

class WifiListLiveData @RequiresPermission(allOf = [ACCESS_WIFI_STATE, CHANGE_WIFI_STATE]) constructor() : LiveData<List<ScanResult>?>() {

    private val wifiManager: WifiManager by lazy(LazyThreadSafetyMode.NONE) {
        application.wifiManager
    }

    @Suppress("DEPRECATION")
    @Deprecated("The ability for apps to trigger scan requests will be removed in a future release.")
    fun startScan() {
        if (!wifiManager.startScan()) {
            value = null
        }
    }

    override fun onActive() {
        application.registerReceiver(wifiScanReceiver, IntentFilter(WifiManager.SCAN_RESULTS_AVAILABLE_ACTION))
    }

    override fun onInactive() {
        application.unregisterReceiver(wifiScanReceiver)
    }

    private val wifiScanReceiver = object : BroadcastReceiver() {
        @SuppressLint("MissingPermission")
        override fun onReceive(context: Context, intent: Intent) {
            if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M ||
                intent.getBooleanExtra(WifiManager.EXTRA_RESULTS_UPDATED, false)
            ) {
                value = wifiManager.scanResults
            }
        }
    }
}

@RequiresPermission(allOf = [ACCESS_WIFI_STATE, CHANGE_WIFI_STATE])
fun Context.networkStateFlow(): Flow<Boolean> = callbackFlow {
    val callback = object : ConnectivityManager.NetworkCallback() {
        override fun onAvailable(network: Network) {
            trySend(true)
        }

        override fun onLost(network: Network) {
            trySend(false)
        }
    }
    val manager = getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
    manager.registerNetworkCallback(NetworkRequest.Builder().run {
        addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
        build()
    }, callback)

    awaitClose { // Suspends until channel closed
        manager.unregisterNetworkCallback(callback)
    }
}

/**
 * Created by mat on 14/12/15.
 */
class PortScan(private var address: InetAddress) {
    private var method = METHOD_TCP
    private var noThreads = 50
    
    private var timeOutMillis = 1000
    private var cancelled = false
    private var ports = ArrayList<Int>()
    private val openPortsFound = ArrayList<Int>()
    private var portListener: PortListener? = null

    interface PortListener {
        fun onResult(portNo: Int, open: Boolean)
        fun onFinished(openPorts: ArrayList<Int>?)
    }

    /**
     * Sets the timeout for each port scanned
     *
     *
     * If you raise the timeout you may want to consider increasing the thread count [.setNoThreads] to compensate.
     * We can afford to have quite a high thread count as most of the time the thread is just sitting
     * idle and waiting for the socket to timeout.
     *
     * @param timeOutMillis - the timeout for each ping in milliseconds
     * Recommendations:
     * Local host: 20 - 500 ms - can be very fast as request doesn't need to go over network
     * Local network 500 - 2500 ms
     * Remote Scan 2500+ ms
     * @return this object to allow chaining
     */
    fun setTimeOutMillis(timeOutMillis: Int): PortScan {
        require(timeOutMillis >= 0) { "Timeout cannot be less than 0" }
        this.timeOutMillis = timeOutMillis
        return this
    }

    /**
     * Scan the ports to scan
     *
     * @param port - the port to scan
     * @return this object to allow chaining
     */
    fun setPort(port: Int): PortScan {
        ports.clear()
        validatePort(port)
        ports.add(port)
        return this
    }

    /**
     * Scan the ports to scan
     *
     * @param ports - the ports to scan
     * @return this object to allow chaining
     */
    fun setPorts(ports: ArrayList<Int>): PortScan {

        // Check all ports are valid
        for (port in ports) {
            validatePort(port)
        }
        this.ports = ports
        return this
    }

    /**
     * Scan the ports to scan
     *
     * @param portString - the ports to scan (comma separated, hyphen denotes a range). For example:
     * "21-23,25,45,53,80"
     * @return this object to allow chaining
     */
    fun setPorts(portString1: String): PortScan {
        var portString = portString1
        ports.clear()
        val ports = ArrayList<Int>()
        
        portString = portString.substring(portString.indexOf(":") + 1, portString.length)
        for (x in portString.split(",").toTypedArray()) {
            if (x.contains("-")) {
                val start = x.split("-").toTypedArray()[0].toInt()
                val end = x.split("-").toTypedArray()[1].toInt()
                validatePort(start)
                validatePort(end)
                require(end > start) { "Start port cannot be greater than or equal to the end port" }
                for (j in start..end) {
                    ports.add(j)
                }
            } else {
                val start = x.toInt()
                validatePort(start)
                ports.add(start)
            }
        }
        this.ports = ports
        return this
    }

    /**
     * Checks and throws exception if port is not valid
     *
     * @param port - the port to validate
     */
    private fun validatePort(port: Int) {
        require(port >= 1) { "Start port cannot be less than 1" }
        require(port <= 65535) { "Start cannot be greater than 65535" }
    }

    /**
     * Scan all privileged ports
     *
     * @return this object to allow chaining
     */
    fun setPortsPrivileged(): PortScan {
        ports.clear()
        for (i in 1..1023) {
            ports.add(i)
        }
        return this
    }

    /**
     * Scan all ports
     *
     * @return this object to allow chaining
     */
    fun setPortsAll(): PortScan {
        ports.clear()
        for (i in 1..65535) {
            ports.add(i)
        }
        return this
    }

    private fun setAddress(address: InetAddress) {
        this.address = address
    }

    private fun setDefaultThreadsAndTimeouts() {
        // Try and work out automatically what kind of host we are scanning
        // local host (this device) / local network / remote
        if (isIpAddressLocalhost(address)) {
            // If we are scanning a the localhost set the timeout to be very short so we get faster results
            // This will be overridden if user calls setTimeoutMillis manually.
            timeOutMillis = TIMEOUT_LOCALHOST
            noThreads = DEFAULT_THREADS_LOCALHOST
        } else if (isIpAddressLocalNetwork(address)) {
            // Assume local network (not infallible)
            timeOutMillis = TIMEOUT_LOCALNETWORK
            noThreads = DEFAULT_THREADS_LOCALNETWORK
        } else {
            // Assume remote network timeouts
            timeOutMillis = TIMEOUT_REMOTE
            noThreads = DEFAULT_THREADS_REMOTE
        }
    }

    /**
     * @param noThreads set the number of threads to work with, note we default to a large number
     * as these requests are network heavy not cpu heavy.
     * @return self
     * @throws IllegalArgumentException - if no threads is less than 1
     */
    @Throws(IllegalArgumentException::class)
    fun setNoThreads(noThreads: Int): PortScan {
        require(noThreads >= 1) { "Cannot have less than 1 thread" }
        this.noThreads = noThreads
        return this
    }

    /**
     * Set scan method, either TCP or UDP
     *
     * @param method - the transport method to use to scan, either PortScan.METHOD_UDP or PortScan.METHOD_TCP
     * @return this object to allow chaining
     * @throws IllegalArgumentException - if invalid method
     */
    private fun setMethod(method: Int): PortScan {
        when (method) {
            METHOD_UDP, METHOD_TCP -> this.method = method
            else -> throw IllegalArgumentException("Invalid method type $method")
        }
        return this
    }

    /**
     * Set scan method to UDP
     *
     * @return this object to allow chaining
     */
    fun setMethodUDP(): PortScan {
        setMethod(METHOD_UDP)
        return this
    }

    /**
     * Set scan method to TCP
     *
     * @return this object to allow chaining
     */
    fun setMethodTCP(): PortScan {
        setMethod(METHOD_TCP)
        return this
    }

    /**
     * Cancel a running ping
     */
    fun cancel() {
        cancelled = true
    }

    /**
     * Perform a synchronous (blocking) port scan and return a list of open ports
     *
     * @return - ping result
     */
    fun doScan(): ArrayList<Int> {
        cancelled = false
        openPortsFound.clear()
        val executor = Executors.newFixedThreadPool(noThreads)
        for (portNo in ports) {
            val worker: Runnable = PortScanRunnable(address, portNo, timeOutMillis, method)
            executor.execute(worker)
        }

        // This will make the executor accept no new threads
        // and finish all existing threads in the queue
        executor.shutdown()
        // Wait until all threads are finish
        try {
            executor.awaitTermination(1, TimeUnit.HOURS)
        } catch (e: InterruptedException) {
            e.printStackTrace()
        }
        openPortsFound.sort()
        return openPortsFound
    }

    /**
     * Perform an asynchronous (non-blocking) port scan
     *
     * @param portListener - the listener to fire portscan results to.
     * @return - this object so we can cancel the scan if needed
     */
    fun doScan(portListener: PortListener?): PortScan {
        this.portListener = portListener
        openPortsFound.clear()
        cancelled = false
        Thread {
            val executor = Executors.newFixedThreadPool(noThreads)
            for (portNo in ports) {
                val worker: Runnable =
                    PortScanRunnable(address, portNo, timeOutMillis, method)
                executor.execute(worker)
            }

            // This will make the executor accept no new threads
            // and finish all existing threads in the queue
            executor.shutdown()
            // Wait until all threads are finish
            try {
                executor.awaitTermination(1, TimeUnit.HOURS)
            } catch (e: InterruptedException) {
                e.printStackTrace()
            }
            if (portListener != null) {
                openPortsFound.sort()
                portListener.onFinished(openPortsFound)
            }
        }.start()
        return this
    }

    @Synchronized
    private fun portScanned(port: Int, open: Boolean) {
        if (open) {
            openPortsFound.add(port)
        }
        if (portListener != null) {
            portListener!!.onResult(port, open)
        }
    }

    private inner class PortScanRunnable internal constructor(
        private val address: InetAddress,
        private val portNo: Int,
        private val timeOutMillis: Int,
        private val method: Int
    ) :
        Runnable {
        override fun run() {
            if (cancelled) return
            when (method) {
                METHOD_UDP -> portScanned(
                    portNo, scanUpdAddress(
                        address,
                        portNo,
                        timeOutMillis
                    )
                )
                METHOD_TCP -> portScanned(
                    portNo, scanTcpAddress(
                        address,
                        portNo,
                        timeOutMillis
                    )
                )
                else -> throw IllegalArgumentException("Invalid method")
            }
        }
    }

    companion object {
        private const val TIMEOUT_LOCALHOST = 25
        private const val TIMEOUT_LOCALNETWORK = 1000
        private const val TIMEOUT_REMOTE = 2500
        private const val DEFAULT_THREADS_LOCALHOST = 7
        private const val DEFAULT_THREADS_LOCALNETWORK = 50
        private const val DEFAULT_THREADS_REMOTE = 50
        private const val METHOD_TCP = 0
        private const val METHOD_UDP = 1

        /**
         * Set the address to ping
         *
         * @param address - Address to be pinged
         * @return this object to allow chaining
         * @throws UnknownHostException - if no IP address for the
         * `host` could be found, or if a scope_id was specified
         * for a global IPv6 address.
         */
        @Throws(UnknownHostException::class)
        fun onAddress(address: String?): PortScan {
            return onAddress(InetAddress.getByName(address))
        }

        /**
         * Set the address to ping
         *
         * @param ia - Address to be pinged
         * @return this object to allow chaining
         */
        fun onAddress(ia: InetAddress): PortScan {
            val portScan = PortScan(ia)
            portScan.setDefaultThreadsAndTimeouts()
            return portScan
        }
    }
}

/**
 * Created by mat on 09/12/15.
 *
 * Tested this and it wakes my computer up :)
 *
 * Ref: http://www.jibble.org/wake-on-lan/
 */
class WakeOnLan  // This class is not to be instantiated
private constructor() {
    private var ipStr: String? = null
    private var inetAddress: InetAddress? = null
    private var macStr: String? = null
    private var port = DEFAULT_PORT
    private var timeoutMillis = DEFAULT_TIMEOUT_MILLIS
    private var noPackets = DEFAULT_NO_PACKETS

    interface WakeOnLanListener {
        fun onSuccess()
        fun onError(e: java.lang.Exception?)
    }

    /**
     * Set the mac address of the device to wake
     *
     * @param macStr - The MAC address of the device to be woken (Required)
     * @return this object to allow chaining
     */
    fun withMACAddress(macStr: String): WakeOnLan {
        this.macStr = macStr
        return this
    }

    /**
     * Sets the port to send the packet to, default is 9
     *
     * @param port - the port for the wol packet
     * @return this object to allow chaining
     */
    fun setPort(port: Int): WakeOnLan {
        require(!(port <= 0 || port > 65535)) { "Invalid port $port" }
        this.port = port
        return this
    }

    /**
     * Sets the number of packets to send, this is to overcome the flakiness of networks
     *
     * @param noPackets - the numbe of packets to send
     * @return this object to allow chaining
     */
    fun setNoPackets(noPackets: Int): WakeOnLan {
        require(noPackets > 0) { "Invalid number of packets to send $noPackets" }
        this.noPackets = noPackets
        return this
    }

    /**
     * Sets the number milliseconds for the timeout on the socket send
     *
     * @param timeoutMillis - the timeout in milliseconds
     * @return this object to allow chaining
     */
    fun setTimeout(timeoutMillis: Int): WakeOnLan {
        require(timeoutMillis > 0) { "Timeout cannot be less than zero" }
        this.timeoutMillis = timeoutMillis
        return this
    }

    /**
     * Synchronous call of the wake method. Note that this is a network request and should not be
     * performed on the UI thread
     *
     * @throws IOException - Thrown from socket errors
     */
    @Throws(IOException::class)
    fun wake() {
        require(!(ipStr == null && inetAddress == null)) { "You must declare ip address or supply an inetaddress" }
        if (macStr == null) {
            throw NullPointerException("You did not supply a mac address with withMac(...)")
        }
        if (ipStr != null) {
            sendWakeOnLan(ipStr!!, macStr!!, port, timeoutMillis, noPackets)
        } else {
            inetAddress?.let { sendWakeOnLan(it, macStr!!, port, timeoutMillis, noPackets) }
        }
    }

    /**
     * Asynchronous call of the wake method. This will be performed on the background thread
     * and optionally fire a listener when complete, or when an error occurs
     *
     * @param wakeOnLanListener - listener to call on result
     */
    fun wake(wakeOnLanListener: WakeOnLanListener?) {
        val thread = Thread {
            try {
                wake()
                wakeOnLanListener?.onSuccess()
            } catch (e: IOException) {
                wakeOnLanListener?.onError(e)
            }
        }
        thread.start()
    }

    companion object {
        const val DEFAULT_PORT = 9
        const val DEFAULT_TIMEOUT_MILLIS = 10000
        const val DEFAULT_NO_PACKETS = 5

        /**
         * Set the ip address to wake
         *
         * @param ipStr - IP Address to be woke
         * @return this object to allow chaining
         */
        fun onIp(ipStr: String): WakeOnLan {
            val wakeOnLan = WakeOnLan()
            wakeOnLan.ipStr = ipStr
            return wakeOnLan
        }

        /**
         * Set the address to wake
         *
         * @param inetAddress - InetAddress to be woke
         * @return this object to allow chaining
         */
        fun onAddress(inetAddress: InetAddress): WakeOnLan {
            val wakeOnLan = WakeOnLan()
            wakeOnLan.inetAddress = inetAddress
            return wakeOnLan
        }

        /**
         * Send a Wake-On-Lan packet
         *
         * @param ipStr         - IP String to send wol packet to
         * @param macStr        - MAC address to wake up
         * @param port          - port to send packet to
         * @param timeoutMillis - timeout (millis)
         * @param packets       - number of packets to send
         *
         * @throws IllegalArgumentException - invalid ip or mac
         * @throws IOException - error sending packet
         */
        /**
         * Send a Wake-On-Lan packet to port 9 using default timeout of 10s
         *
         * @param ipStr  - IP String to send to
         * @param macStr - MAC address to wake up
         *
         * @throws IllegalArgumentException - invalid ip or mac
         * @throws IOException - error sending packet
         */
        @JvmOverloads
        @Throws(java.lang.IllegalArgumentException::class, IOException::class)
        fun sendWakeOnLan(
            ipStr: String,
            macStr: String,
            port: Int = DEFAULT_PORT,
            timeoutMillis: Int = DEFAULT_TIMEOUT_MILLIS,
            packets: Int = DEFAULT_NO_PACKETS
        ) {
            val address = InetAddress.getByName(ipStr)
            sendWakeOnLan(address, macStr, port, timeoutMillis, packets)
        }

        /**
         * Send a Wake-On-Lan packet
         *
         * @param address       - InetAddress to send wol packet to
         * @param macStr        - MAC address to wake up
         * @param port          - port to send packet to
         * @param timeoutMillis - timeout (millis)
         * @param packets       - number of packets to send
         *
         * @throws IllegalArgumentException - invalid ip or mac
         * @throws IOException - error sending packet
         */
        @Throws(java.lang.IllegalArgumentException::class, IOException::class)
        fun sendWakeOnLan(
            address: InetAddress,
            macStr: String,
            port: Int,
            timeoutMillis: Int,
            packets: Int
        ) {
            require(!(port <= 0 || port > 65535)) { "Invalid port $port" }
            require(packets > 0) { "Invalid number of packets to send $packets" }
            val macBytes: ByteArray = getMacBytes(macStr)
            val bytes = ByteArray(6 + 16 * macBytes.size)
            for (i in 0..5) {
                bytes[i] = 0xff.toByte()
            }
            run {
                var i = 6
                while (i < bytes.size) {
                    System.arraycopy(macBytes, 0, bytes, i, macBytes.size)
                    i += macBytes.size
                }
            }
            val packet = DatagramPacket(bytes, bytes.size, address, port)

            // Wake on lan is unreliable so best to send the packet a few times
            for (i in 0 until packets) {
                val socket = DatagramSocket()
                socket.soTimeout = timeoutMillis
                socket.send(packet)
                socket.close()
            }
        }
    }
}

val StringValidator.Companion.IP4Rule: StringValidator
    get() = StringValidator{ it->
        NetworkExt.isIP4(it)
}

val StringValidator.Companion.IP4NetRule: StringValidator
    get() = StringValidator{ it->
        NetworkExt.isIP4Net(it)
    }

val StringValidator.Companion.IP6Rule: StringValidator
    get() = StringValidator{ it->
        NetworkExt.isIP6(it)
    }

val StringValidator.Companion.IP6NetRule: StringValidator
    get() = StringValidator{ it->
        NetworkExt.isIP6Net(it)
    }
